#include "pd/converter.h"

#include <string.h>
#include <wand/wand_api.h>

// #define ENABLE_DEBUG

#ifdef ENABLE_DEBUG

#define DEBUG(...)			    \
    do {				    \
	fprintf(stderr, "gm: "__VA_ARGS__); \
    } while (0);

#else

#define DEBUG(...)

#endif

typedef struct GMConverter
{
    Converter parent;

    /* HACK:
     * GraphicsMagick will prepend an image if there is no previous
     * image in the list when calling MagickAddImage, so we keep a
     * reference to the first frame so that we can add it after the
     * second frame has been read.
     */
    MagickWand* firstFrame; 
    MagickWand* wand;
    char* format;
} GMConverter;

static Converter* create_converter(const char *format);

bool initialize_converter_library(ConverterInfo* info)
{
    info->name = "GraphicsMagick Converter";
    info->api_version = PD_CONVERTER_API_VERSION;
    info->create_converter = create_converter;

    DEBUG("initializing library\n");
    InitializeMagick(NULL);
    return true;
}

void deinitialize_converter_library(ConverterInfo* info)
{
    DEBUG("deinitializing library\n");
    DestroyMagick();
}

static bool gm_converter_append_frame(Converter* converter, const char* path)
{
    DEBUG("appending frame: %s\n", path);

    if (!converter)
    {
	DEBUG("NULL converter");
	return false;
    }

    GMConverter* gm = (GMConverter*)converter;

    /**
       Up until GraphicsMagick 1.3.40, it was possible to just call MagickReadImage
       and new images would be added to the internal list in a way which made it
       easy to create GIFs.  GM 1.3.40 changed the behaviour of a separate function
       which meant this (admittedly) undocumented feature broke.  Bob has kindly
       fixed this and it works again from version 1.3.43.  That said, I won't go
       back to using MagickReadImage as the current method seems more robust to
       change.

       https://sourceforge.net/p/graphicsmagick/bugs/735/
    **/
    
    if (!gm->firstFrame)
    {
	gm->firstFrame = NewMagickWand();
	MagickReadImage(gm->firstFrame, path);
	MagickResetIterator(gm->firstFrame);
	return true;
    }

    if (!gm->wand)
    {
	gm->wand = NewMagickWand();
	MagickReadImage(gm->wand, path);
	MagickResetIterator(gm->wand);
	MagickSetImageFormat(gm->wand, gm->format);
	DEBUG("Setting format: %s\n", gm->format);
	return MagickAddImage(gm->wand, gm->firstFrame) == MagickPass;
    }

    MagickWand* tempWand = NewMagickWand();
    if (MagickReadImage(tempWand, path) != MagickPass)
    {
	DEBUG("Failed to read frame: %s\n", path);
	DestroyMagickWand(tempWand);
	return false;
    }

    int status = MagickAddImage(gm->wand, tempWand);
    
    /* MagickAddImage copies the "add_wand" */
    DestroyMagickWand(tempWand);

    return status == MagickPass;
}

static void gm_converter_set_frame_delay(Converter* converter, unsigned delay)
{
//    DEBUG("setting frame delay: %u ms (%u cs)\n", delay, delay / 10);

    if (!converter)
	return;

    GMConverter* gm = (GMConverter*)converter;
    unsigned delay_cs = delay / 10;

    if (!gm->firstFrame)
	return;

    if (!gm->wand)
    {
	MagickSetImageDelay(gm->firstFrame, delay_cs);
	return;
    }

    MagickSetImageDelay(gm->wand, delay_cs);
}

static bool gm_converter_write(Converter* converter, const char* path)
{
    if (!converter)
    {
	DEBUG("Attempting to write with a NULL converter\n");
	return false;
    }

    GMConverter* gm = (GMConverter*)converter;

    /* DEBUG("writing to %s\n", path); */

    MagickResetIterator(gm->wand);
    return MagickWriteImages(gm->wand, path, MagickTrue) == MagickPass;
}

static void gm_converter_dispose(Converter* converter)
{
    GMConverter* gm = (GMConverter*)converter;

    DEBUG("destroying converter\n");

    if (!converter)
	return;

    if (gm->firstFrame)
	DestroyMagickWand(gm->firstFrame);

    if (gm->wand)
	DestroyMagickWand(gm->wand);

    if (gm->format)
	free(gm->format);

    free(gm);
}

/**
   Create a new converter that pixiv_down can use.

   The returned Converter must have all the functions set.
   
   @param format The output format.
   @return NULL if failed, otherwise a Converter.
**/
static Converter* create_converter(const char *format)
{
    GMConverter *converter = NULL;

    DEBUG("creating converter\n");

    // XXX: probably don't need to allocate this.
    converter = calloc(1, sizeof *converter);

    if (!converter)
    {
	DEBUG("Failed to initialize GM converter\n");
	return NULL;
    }

    converter->firstFrame = NULL;
    converter->wand = NULL;
    converter->format = calloc(strlen(format) + 1, 1);
    strncpy(converter->format, format, strlen(format));

    converter->parent.append_frame = gm_converter_append_frame;
    converter->parent.set_frame_delay = gm_converter_set_frame_delay;
    converter->parent.write = gm_converter_write;
    converter->parent.dispose = gm_converter_dispose;

    return &converter->parent;
}
